#include "StdAfx.h"
#include <assert.h>
#include <string>
#include "Common/IntToString.h"
#include "Common/MyInitGuid.h"
#include "Common/StringConvert.h"

#include "Windows/DLL.h"
#include "Windows/FileDir.h"
#include "Windows/FileFind.h"
#include "Windows/FileName.h"
#include "Windows/PropVariant.h"
#include "Windows/PropVariantConversions.h"

#include "../../Common/FileStreams.h"
#include "../../Archive/IArchive.h"
#include "../../IPassword.h"
#include "../../MyVersion.h"
#include "../Common/LoadCodecs.h"
#include "../Common/OpenArchive.h"
#include "../Common/ArchiveOpenCallback.h"

using namespace std;
using namespace NWindows;

namespace http
{
	namespace server
	{
		class server
		{
		public:
			static void* new_server(const std::string& doc_root);
			static unsigned short get_server_port(void* server_);
			static void add_virtual_resource(void* server_, const std::string& path, void* tag);
			static void run_server(void* server_);
			static void delete_server(void* server_);
		};

		void test();
	}
}

HINSTANCE g_hInstance = 0;

static const char *kCopyrightString = 
	"Harber -- a Hyper ARchive BrowsER based on 7-Zip";

static const char *kHelpString =
"Usage: Harber.exe archive\n"
"Examples:\n"
"  Harber a.7z      : browse 'a.7z'\n"
"  Harber b.rar     : browse 'b.rar'\n"
"  Harber c.zip     : browse 'c.zip'\n";

#define DIM(a) (sizeof(a)/sizeof(a[0]))

#ifdef _WIN32
#ifndef _UNICODE
bool g_IsNT = false;
static inline bool IsItWindowsNT()
{
	OSVERSIONINFO versionInfo;
	versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
	if (!::GetVersionEx(&versionInfo))
		return false;
	return (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT);
}
#endif
#endif

void PrintString(const UString &s)
{
	printf("%s", (LPCSTR)GetOemString(s));
}

void PrintString(const AString &s)
{
	printf("%s", (LPCSTR)s);
}

void PrintNewLine()
{
	PrintString("\n");
}

void PrintStringLn(const AString &s)
{
	PrintString(s);
	PrintNewLine();
}

void PrintError(const AString &s)
{
	PrintNewLine();
	PrintString(s);
	PrintNewLine();
}

//static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
//{
//	NCOM::CPropVariant prop;
//	RINOK(archive->GetProperty(index, propID, &prop));
//	if (prop.vt == VT_BOOL)
//		result = VARIANT_BOOLToBool(prop.boolVal);
//	else if (prop.vt == VT_EMPTY)
//		result = false;
//	else
//		return E_FAIL;
//	return S_OK;
//}
//
//static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
//{
//	return IsArchiveItemProp(archive, index, kpidIsDir, result);
//}


static const wchar_t *kEmptyFileAlias = L"[Content]";


//////////////////////////////////////////////////////////////
// Archive Open callback class

class CArchiveOpenCallback:
	public IArchiveOpenCallback,
	public ICryptoGetTextPassword,
	public CMyUnknownImp
{
public:
	MY_UNKNOWN_IMP1(ICryptoGetTextPassword);

	STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes);
	STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes);

	STDMETHOD(CryptoGetTextPassword)(BSTR *password);

	bool PasswordIsDefined;
	UString Password;

	CArchiveOpenCallback() : PasswordIsDefined(false) {}
};

STDMETHODIMP CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */)
{
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */)
{
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password)
{
	if (!PasswordIsDefined)
	{
		// You can ask real password here from user
		// Password = GetPassword(OutStream);
		// PasswordIsDefined = true;
		PrintError("Password is not defined");
		return E_ABORT;
	}
	return StringToBstr(Password, password);
}


//////////////////////////////////////////////////////////////
// Archive Extracting callback class

static const wchar_t *kCantDeleteOutputFile = L"ERROR: Can not delete output file ";

static const char *kTestingString    =  "Testing     ";
static const char *kExtractingString =  "Extracting  ";
static const char *kSkippingString   =  "Skipping    ";

static const char *kUnsupportedMethod = "Unsupported Method";
static const char *kCRCFailed = "CRC Failed";
static const char *kDataError = "Data Error";
static const char *kUnknownError = "Unknown Error";

void* server;
CCodecs codecs;
CInFileStream* inFile;
COutFileStream *outFile;
CMyComPtr<IInStream> inStream;
CMyComPtr<IInArchive> archive;
CMyComPtr<ISequentialOutStream> outStream;


void open_archive(const char* fn)
{
	UString ifn=GetUnicodeString(fn, CP_OEMCP);
	inStream = inFile = new CInFileStream;
	if (!inFile->Open(ifn))
	{
		PrintError("Can not open input file");
		return;
	}

	int r;
	UString s;
	CArchiveOpenCallback openCallback;
	CIntVector formatIndices;
	openCallback.AddRef();
	HRESULT h=OpenArchive(&codecs, -1, inStream, s, &archive, r, s, &openCallback);
	if (h!=S_OK)
	{
		PrintError("Can not get input class object");
		getchar();
		abort();
	}

	UInt32 NumItems;
	archive->GetNumberOfItems(&NumItems);
	NWindows::NCOM::CPropVariant path;
	for (UInt32 i=0; i<NumItems; ++i)
	{
		archive->GetProperty(i, kpidPath, &path);
		UString p=ConvertPropVariantToString(path);
		http::server::server::add_virtual_resource(
			server, (LPCSTR)GetOemString(p), (void*)i);
	}
}


class CArchiveExtractCallback:
	public IArchiveExtractCallback,
	public ICryptoGetTextPassword,
	public ISequentialOutStream,
	public CMyUnknownImp
{
public:
	MY_UNKNOWN_IMP2(ICryptoGetTextPassword, ISequentialOutStream);

	UInt32 NumErrors;
	std::string& content_;
	CArchiveExtractCallback(std::string& content) :
		content_(content)
	{
		NumErrors=0;
	}

	// IProgress
	STDMETHOD(SetTotal)(UInt64 size)						{return S_OK;}
	STDMETHOD(SetCompleted)(const UInt64 *completeValue)	{return S_OK;}

	// ISequentialOutStream
	STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize)
	{
		content_.append((const char*)data, size);
		*processedSize = size;
		return S_OK;
	}

	STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)
	{
		if (askExtractMode!=::NArchive::NExtract::NAskMode::kExtract)
			return S_OK;

		*outStream = this;
		AddRef();
		return S_OK;
	}
	STDMETHOD(PrepareOperation)(Int32 askExtractMode)
	{
		//switch (askExtractMode)
		//{
		//case NArchive::NExtract::NAskMode::kExtract:  PrintString(kExtractingString); break;
		//case NArchive::NExtract::NAskMode::kTest:  PrintString(kTestingString); break;
		//case NArchive::NExtract::NAskMode::kSkip:  PrintString(kSkippingString); break;
		//};
		return S_OK;
	}

	STDMETHOD(SetOperationResult)(Int32 operationResult)
	{
		switch(operationResult)
		{
		case NArchive::NExtract::NOperationResult::kOK:
			break;
		default:
			{
				NumErrors++;
				PrintString("     ");
				switch(operationResult)
				{
				case NArchive::NExtract::NOperationResult::kUnSupportedMethod:
					PrintString(kUnsupportedMethod);
					break;
				case NArchive::NExtract::NOperationResult::kCRCError:
					PrintString(kCRCFailed);
					break;
				case NArchive::NExtract::NOperationResult::kDataError:
					PrintString(kDataError);
					break;
				default:
					PrintString(kUnknownError);
				}
			}
		}
		//PrintNewLine();
		//_pipeStream.Release();
		return S_OK;
	}

	STDMETHOD(CryptoGetTextPassword)(BSTR *password)
	{
		return StringToBstr(L"", password);
	}
};

void serve_archive_content(std::string& content, void* tag)
{
	UInt32 index=(UInt32)tag;
	CArchiveExtractCallback ecb(content);
	ecb.AddRef();

	archive->Extract(&index, 1, false, &ecb);
}


#pragma warning(disable: 4996)
//////////////////////////////////////////////////////////////////////////
// Main function
int MY_CDECL main(int argc, char* argv[])
{
#ifdef _WIN32
#ifndef _UNICODE
	g_IsNT = IsItWindowsNT();
#endif
#endif
	//http::server::test();
	//return 0;

	PrintStringLn(kCopyrightString);

	if (argc!=2)
	{
		PrintStringLn(kHelpString);
		return 1; 
	}

	if (codecs.Load()!=S_OK)
	{
		PrintError("7z.dll not found!");
		return 1;
	}

	server=http::server::server::new_server("HTML/");
	unsigned short port=http::server::server::get_server_port(server);
	printf("server listening @ http://127.0.0.1:%u/\n", port);

	codecs.AddRef();
	open_archive(argv[1]);
	char buf[1024];
	sprintf(buf, "/c start http://127.0.0.1:%u//index.html", port);
	::ShellExecute(NULL, NULL, "cmd.exe", buf, NULL, SW_HIDE);
	http::server::server::run_server(server);
	http::server::server::delete_server(server);
	return 0;
}
